package com.colloquial.arithcode2;

import java.io.ByteArrayOutputStream;

public class DescompresorAritmetico {
	
	private BitArray arrayAnalizado;
	public ByteArrayOutputStream getOut() {
		return out;
	}

	private BitArrayStream arrayComprimido;
	private BitArray  piso ;
	private BitArray techo;
	private BitArray rango;
	private TablaProbabilidades rs;
	private int posActual;
	private ByteArrayOutputStream out;
	private int overflow;
	private int UltimoDescomprimido;
	private int underflow;
	private BitArrayStream bitAux;
	
	
	
	public DescompresorAritmetico(BitArrayStream arrayCompleto) throws BitArrayException{
		this.arrayComprimido = arrayCompleto;
		this.arrayAnalizado = new BitArray();
		this.piso = new BitArray();
		this.overflow = 0;
		this.underflow = 0;
		this.piso.SetPiso();
		this.techo = new BitArray();
		this.techo.SetTecho();
		this.rango = new BitArray();
		this.rango.SetNumero((this.techo.GetNumero() - this.piso.GetNumero()));
		this.out = new ByteArrayOutputStream();
		calcularArrayActual();
	}
	
	//Me carga los 32 primeros bits q son los q tengo q usar
	private void calcularArrayActual(){
		for (int i = 0; i < 32 && i < this.arrayComprimido.Lenght(); i++) {
			boolean b = this.arrayComprimido.BitAt(i);
			this.arrayAnalizado.PutBitOF(b);
		}
		this.posActual = 32;
		System.out.println("El Array analizado es:");
		System.out.println(this.arrayAnalizado.ToBinaryString());
		System.out.println(this.arrayAnalizado.GetNumero());
		System.out.println("El piso es"+this.piso.ToBinaryString());
		System.out.println("El techo es"+this.techo.ToBinaryString());
	}
	
	
	public boolean descomprimirSiguientePaso(TablaProbabilidades rs2) throws Exception{
		//Devuelve True cuando se llego al fin del archivo
		this.rs = rs2;
		 return getSimboloActual();	
	}
	
	public boolean getSimboloActual() throws Exception{

		byte[] ba= new byte[2];
		int it =0;
		long res = 0;
		long contador = 0L;
		boolean encontrado = false;
		while (!encontrado){
			res = (long) (this.piso.GetNumero() -1 +(double) (((this.rs.GetSimbolo(it).getProbabilidad()+contador)* (this.rango.GetNumero()+1)) /this.rs.getTotal()));
			this.techo.SetNumero(res);
			if (this.techo.GetNumero()>= this.arrayAnalizado.GetNumero()){
				encontrado = true;
				res = (long) (this.piso.GetNumero() + ((contador * (this.rango.GetNumero()+1))/this.rs.getTotal()));
				this.piso.SetNumero(res);
				//rs.aumentarFrecuencia(it);
			}else{
				encontrado = false;
				contador = contador + this.rs.GetSimbolo(it).getProbabilidad();
				it++;
			}
		}
		if(this.arrayAnalizado.GetNumero() != this.techo.GetNumero()){
			System.out.println("" +
			"El piso es"+this.piso.GetNumero());
			System.out.println("El techo es"+this.techo.GetNumero());
			ba = ConversionesTiposDatos.ObtenerBytesCaracterUTF16BE(it);
			System.out.println("El caracter q se descomprime es el nro "+it);
			System.out.println(new String(ba,0,ba.length,"UTF-16BE"));
			this.recalcularArray();
			this.out.write(ba);
			this.rango.SetNumero((this.techo.GetNumero() - this.piso.GetNumero()));
			this.UltimoDescomprimido = it;
		}else {
			System.out.println("   ENTRO AL EOF  ");
			ba = ConversionesTiposDatos.ObtenerBytesCaracterUTF16BE(it);
			System.out.println("El caracter q se descomprime es el nro "+it);
			System.out.println(new String(ba,0,ba.length,"UTF-16BE"));
			
			this.out.write(ba);
			return true;
		}
			return false;
		}


	public int getUltimoDescomprimido() {
		return UltimoDescomprimido;
	}

	private void recalcularArray() throws Exception {
		this.overflow = this.overflow();
		System.out.println("El overflow es "+this.overflow);
		if (this.overflow > 0){
			int j = 0;
			int i = this.overflow;
			while (i>0){
				//del analizado siempre el 0 porq se va moviendo y me queda en el 0 el q quiero ver
				if((this.arrayAnalizado.BitAt(0) == this.bitAux.BitAt(j))) {
					this.arrayAnalizado.PutBitOF(this.arrayComprimido.BitAt(posActual));
					posActual++;//voy agregando al analizado desde donde me quede
				}else  throw new Exception("NO ES IGUAL EL OUTPUT QUE LA ENTRADA");
				j++;
				i--;
			}
			this.piso = BitArray.CompletarBits(this.overflow, this.piso,true ,false ); //completo
			this.techo = BitArray.CompletarBits(this.overflow, this.techo,true ,true );
			this.overflow = 0;
		}
		//hasta aca ya elimine los iguales por underflow y agregue los q siguen para completar
		//hay q ver como eliminar desde el 2do por el underflow
		this.underflow = underflow();
		int i = this.underflow;
		while (i>0){
			//del analizado siempre el 0 porq se va moviendo y me queda en el 0 el q quiero ver
				this.arrayAnalizado.PutBitUF(this.arrayComprimido.BitAt(posActual));
				posActual++;//voy agregando al analizado desde donde me quede
			i--;
		}
		System.out.println("El underflow es "+this.underflow);
		if(this.underflow > 0){//hay underflow, los cuento para luego eliminar y saber cuanto emitir la proxima vez
			this.piso = BitArray.CompletarBits(this.underflow, this.piso,false ,false ); //completo
			this.techo = BitArray.CompletarBits(this.underflow, this.techo,false ,true );
			this.underflow = 0;
		}	
				
		System.out.println("El Array analizado es:");
		System.out.println(this.arrayAnalizado.ToBinaryString());
		System.out.println(this.arrayAnalizado.GetNumero());
	}
	private int underflow() throws Exception {
		int uf = 0;
		boolean iguales;
		if( this.piso.BitAt(0) == this.techo.BitAt(0)) System.out.println("ERROR EN UN UF");
		boolean bpiso = this.piso.BitAt(1);//leo desde el 2 porq los primeros ya se q son distintos porq no hay overflow o si hubo ya se corto
		boolean bpisoSig = false;
		boolean btechoSig = false;
		boolean btecho = this.techo.BitAt(1);
		boolean biento = true;
		iguales = bpiso == btecho;
		int i=2;
		while (iguales == false && biento && i < this.piso.Length()){
			uf++;
			bpisoSig = this.piso.BitAt(i);
			btechoSig = this.techo.BitAt(i);
			iguales = bpiso == btecho;
			i++;
			if (!( bpiso == bpisoSig && btecho ==btechoSig)) biento = false ;
			}
		return uf;
	}

	private int overflow() throws Exception {
		int of = 0;
		int j = 0;
		bitAux = new BitArrayStream();
		boolean iguales;
		boolean aux = false;
		boolean bpiso = this.piso.BitAt(j);
		boolean btecho = this.techo.BitAt(j);
		iguales = bpiso == btecho;
		while (iguales == true && j < this.piso.Length()){//tengo q ver el contador de uf q tenia para ver si emito algo inverso
			of++;
			bitAux.PutBit(piso.BitAt(j));
			for(int i = this.underflow; i > 0; i--){//emito tantos caracteres inversos como uf tenia
				aux = piso.BitAt(j);
				bitAux.PutBit(!aux);
			}
			this.underflow=0;
			j++;
			iguales = this.piso.BitAt(j) == this.techo.BitAt(j);
			}
		return of;
		
	}

	}
